''' miscellaneous helper functions and classes.
'''
import pygame.image
import random

ipython = None
if config.DEVEL:
    try: from IPython.Shell import start as ipython
    except ImportError: pass

def DEBUG(x):
    print x
    return x

try: set = set
except: from sets import Set as set

try:
    any and all
except:
    def any(s):
        ''' return True if any elements of s are "True"
        '''
        for x in s:
            if x: return True
        return False
    
    def all(s):
        ''' return True if all elements of s are "True"
        '''
        for x in s:
            if not x: return False
        return True

class AbstractMethodError(Exception):
    ''' raised when you try to call an abstract method.
    '''
    def __init__(self):
        Exception.__init__(self, 'abstract class method')

def def_abstract_method(name, doc='', frame_depth=1):
    ''' define an abstract method named name with docstring doc.
    '''
    namespace = sys._getframe(frame_depth).f_locals
    exec ('def %s(self): raise AbstractMethodError' % name) in globals(), namespace
    namespace[name].__doc__ = doc

def def_abstract_methods(*sigs):
    ''' define several abstract methods:
        class foo:
            def_abstract_methods(
                ('foo', 'overload this abstract method to frob the bar.'),
                ('bar', 'overload this abstract method to frob the quux.'),
            )
    '''
    for sig in         if isinstance(sig, (tuple, list)): def_abstract_method(sig[0], sig[1], 2)
        elif isinstance(sig, (str, unicode)): def_abstract_method(sig, '', 2)

def load_image(fullname, colorkey=None):
    ''' example: self.image, self.rect = load_image(FOO_IMAGE_FILENAME)
    '''
    try:
        image = pygame.image.load(fullname)
    except pygame.error, message:
        print 'Cannot load image:', fullname
        raise SystemExit, message
    image = image.convert()
    if colorkey is not None:
        if colorkey is -1:
            colorkey = image.get_at((0, 0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image, image.get_rect()

def weighted_choice(*two_tuples):
    ''' >>> weighted_choice(
        ...     (.5, most_common_object),
        ...     (.25, another_object)
        ... )    # returns None with probability .25
        >>> freq = [0 for i in range(10)]
        >>> for i in xrange(10000):freq[weighted_choice(*zip([.1,.1,.1,.2,0,.1,.1,.1,.2,0],range(10)))]+=1
        ...
        >>> freq
        [978, 934, 1019, 1943, 0, 1062, 1041, 964, 2059, 0]
    '''
    x = random.random()
    running_sum = 0
    for probability, obj in two_tuples:
        running_sum += probability
        if running_sum > x:
            return obj

def draw_text(surface, lines, x=0, y=0, font=None, color=None):
    ''' draw multiple lines of text on surface:
        draw_text(self.image, """ a
            b
            c
            """)
        draw_text(self.image, ['a', 'b', 'c'], self.font, self.color)
    '''
    if font is None: font = MAIN.font
    if isinstance(lines, str): lines = lines.split("\n")
    maxwidth = 0
    height = 0
    for line in lines:
        w, h = font.size(line)
        maxwidth = max(maxwidth, w)
        height += h
    for line in lines:
        if line:
            s2 = font.render(line, antialias, color)
            surface.blit(s2, (x, y))
            y += s2.get_size()[1]
        else:
            w, h = font.size(line)
            y += h

def hookable(f):
    ''' decorator to allow hooking a callable.
        hook [pre and post] return values are garbage collected
        decorated f returns f's return value.
    '''
    def hooker(*a, **kw):
        __hook_stop__ = False
        for prehook in hooker.prehooks:
            prehook(*a, **kw)
            if __hook_stop__: return
        __return_value__ = f(*a, **kw)
        for posthook in hooker.posthooks:
            posthook(*a, **kw)
            if __hook_stop__: return __return_value__
        return __return_value__
    hooker.prehooks = []
    hooker.posthooks = []
    def prehook(f):
        hooker.prehooks.append(f)
        return f
    def posthook(f):
        hooker.posthooks.append(f)
        return f
    hooker.prehook = prehook
    hooker.hook = hooker.posthook = posthook
    hooker.__name__ = f.__name__
    hooker.__doc__ = f.__doc__
    return hooker

def was_called_as_hook():
    ''' return True if the calling function was called as a hook.
    '''
    namespace = sys._getframe(2).f_locals
    return ('__return_value__' in namespace) and ('__hook_stop__' in namespace)

def hook_get_return_value():
    ''' if called by a posthook, return the hooked function's return value.
        else, raise an AssertionError.
    '''
    namespace = sys._getframe(2).f_locals
    assert ('__return_value__' in namespace) and ('__hook_stop__' in namespace), "not called by a posthook."
    return namespace['__return_value__']

def hook_stop():
    ''' if called by a hook, set a flag in the hooker function to prevent it from doing anything else, and return True.
        return False if not called in the hook capacity.
    '''
    namespace = sys._getframe(2).f_locals
    if '__hook_stop__' in namespace:
        namespace['__hook_stop__'] = True
        return True
    return False

def count_high_bits(i):
    return sum(gen_bits(i))

def gen_bits(n):
    while n > 0:
        yield bool(n & 1)
        n >>= 1

def get_bit(n, i): return bool(n & (1 << i))
def set_bit(n, i): return n | (1 << i)
def unset_bit(n, i):
    if get_bit(n, i): return n - (1 << i)
    else: return n

def start_daemon_thread(fun, *a, **kw):
    t = threading.Thread(target=fun, args=a, kwargs=kw)
    t.setDaemon(1)
    t.start()
    return t

class odict(dict):
    ''' trivial implementation of an ordered dictionary, for the menu framework.
    '''
    def __init__(self, items=()):
        dict.__init__(self, items)
        self.order = [k for k, v in items]
    
    def __getitem__(self, k):
        if isinstance(k, (int, long)): return dict.__getitem__(self, self.order[k])
        else: dict.__getitem__(self, k)
    
    def __setitem__(self, k, v):
        if k not in self: self.order.append(k)
        dict.__setitem__(self, k, v)
    
    def index(self, k):
        return self.order.index(k)
    
    def __iter__(self):
        for k in self.order: yield k, self[k]
    
    def __getstate__(self):
        return [(k, self[k]) for k in self.order]
    
    __setstate__ = __init__

